// SearchService: fuzzy search and ranking over app/file indexes
const fs = require('fs')

function preprocess(keyword) {
  if (typeof keyword !== 'string') return ''
  return keyword.trim().toLowerCase()
}

function hasExt(key) {
  return key.includes('.')
}

function baseName(name) {
  const idx = name.lastIndexOf('.')
  return idx > 0 ? name.slice(0, idx) : name
}

function scoreApp(item, key) {
  const name = (item.name || '').toLowerCase()
  if (!name || !key) return 0
  if (name === key) return 100
  if (name.startsWith(key)) return 90
  if (name.includes(key)) return 70
  const diff = Math.abs(name.length - key.length)
  return Math.max(20, 60 - diff * 5)
}

function scoreFile(item, key) {
  const name = (item.name || '').toLowerCase()
  if (!name || !key) return 0
  const has = hasExt(key)
  const bn = baseName(name)
  const kb = baseName(key)
  if (has) {
    if (name === key) return 100 // 完全文件名匹配（含扩展名）
    if (name.startsWith(key)) return 80
    if (bn === kb) return 85 // 扩展不同但基本名相同
    if (bn.startsWith(kb)) return 75
    if (bn.includes(kb)) return 60
    return 0 // 含扩展名的关键词不做过度模糊匹配，避免误打开
  } else {
    if (bn === key) return 95 // 基本名完全匹配优先
    if (bn.startsWith(key)) return 85
    if (bn.includes(key)) return 65
    return 0
  }
}

function typeWeight(type) {
  return type === 'app' ? 10 : 0
}

class SearchService {
  constructor(appIndexer, fileIndexer) {
    this.appIndexer = appIndexer
    this.fileIndexer = fileIndexer
  }

  search(keyword) {
    const key = preprocess(keyword)
    if (!key) return []

    const apps = this.appIndexer.getAll() || []
    const files = this.fileIndexer.getAll() || []

    const candidates = []
    for (const it of apps) {
      const base = scoreApp(it, key)
      if (base > 0) {
        candidates.push({ ...it, score: base + typeWeight('app') })
      }
    }
    for (const it of files) {
      const base = scoreFile(it, key)
      if (base > 0) {
        const freshBoost = it.lastModified ? Math.min(10, Math.round((Date.now() - it.lastModified) / (1000 * 60 * 60 * 24)) < 7 ? 5 : 0) : 0
        candidates.push({ ...it, score: base + freshBoost })
      }
    }

    const valid = candidates.filter((c) => {
      try { return fs.existsSync(c.path) } catch (_) { return false }
    })

    valid.sort((a, b) => {
      // 强化完全匹配排序
      const keyLower = preprocess(keyword)
      const exactA = a.name.toLowerCase() === keyLower || baseName(a.name.toLowerCase()) === baseName(keyLower)
      const exactB = b.name.toLowerCase() === keyLower || baseName(b.name.toLowerCase()) === baseName(keyLower)
      if (exactA && !exactB) return -1
      if (!exactA && exactB) return 1
      if (b.score !== a.score) return b.score - a.score
      if (a.type !== b.type) return a.type === 'app' ? -1 : 1
      return a.name.localeCompare(b.name)
    })

    return valid.slice(0, 20)
  }

  searchFiles(keyword) {
    const key = preprocess(keyword)
    if (!key) return []
    const files = this.fileIndexer.getAll() || []
    const candidates = []
    for (const it of files) {
      const base = scoreFile(it, key)
      if (base > 0) candidates.push({ ...it, score: base })
    }
    const valid = candidates.filter((c) => {
      try { return fs.existsSync(c.path) } catch (_) { return false }
    })
    const kb = preprocess(keyword)
    valid.sort((a, b) => {
      const exactA = a.name.toLowerCase() === kb || baseName(a.name.toLowerCase()) === baseName(kb)
      const exactB = b.name.toLowerCase() === kb || baseName(b.name.toLowerCase()) === baseName(kb)
      if (exactA && !exactB) return -1
      if (!exactA && exactB) return 1
      if (b.score !== a.score) return b.score - a.score
      return a.name.localeCompare(b.name)
    })
    return valid.slice(0, 20)
  }

  searchApps(keyword) {
    const key = preprocess(keyword)
    if (!key) return []
    const apps = this.appIndexer.getAll() || []
    const candidates = []
    for (const it of apps) {
      const base = scoreApp(it, key)
      if (base > 0) candidates.push({ ...it, score: base + typeWeight('app') })
    }
    const valid = candidates.filter((c) => {
      try { return fs.existsSync(c.path) } catch (_) { return false }
    })
    const kb = preprocess(keyword)
    valid.sort((a, b) => {
      const exactA = a.name.toLowerCase() === kb
      const exactB = b.name.toLowerCase() === kb
      if (exactA && !exactB) return -1
      if (!exactA && exactB) return 1
      if (b.score !== a.score) return b.score - a.score
      return a.name.localeCompare(b.name)
    })
    return valid.slice(0, 20)
  }

  pickBestByKeyword(results, keyword) {
    const key = preprocess(keyword)
    if (!results.length) return null
    const preferFile = hasExt(key)
    if (preferFile) {
      const exactFile = results.find(r => r.type === 'file' && (r.name.toLowerCase() === key || baseName(r.name.toLowerCase()) === baseName(key)))
      if (exactFile) return exactFile
    } else {
      const exactApp = results.find(r => r.type === 'app' && r.name.toLowerCase() === key)
      if (exactApp) return exactApp
    }
    // fallback to top result
    return results[0]
  }
}

module.exports = { SearchService }
